/**
 ******************************************************************************
 * @file    BSP_Init.c
 * @author  Hsiang Hsu, Zhenyu Yang
 * @version V0.0.2
 * @date    2022年12月25日(V0.0.1)：初始版本
 *          2023年11月27日(V0.0.2)：修改文件类型，设计LL库程序
 * @brief   板级支持包驱动程序
 ******************************************************************************
 **/

/* Includes ------------------------------------------------------------------*/
#include "bsp_init.h"
#include "tusb.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
static void GPIO_Configuration(void);
static void USBD_Configuration(void);
static void UART_Configuration(void);
static void DMA_Configuration(void);
static void CRC_Configuration(void);
static void SPI_Configuration(void);
static void EXTI_Configuration(void);

/* Private functions ---------------------------------------------------------*/

/*******************************************************************************
** 名    称 : GPIO_Configuration
** 功    能 : GPIO 初始化
** 入口参数 : 无
** 出口参数 : 无
*******************************************************************************/
static void GPIO_Configuration(void)
{
  LL_GPIO_InitTypeDef GPIO_InitStruct;

  /* Enable peripheral clock */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOB);
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOC);

  // PB0:输出 LED
  GPIO_InitStruct.Pin = LL_GPIO_PIN_0;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  LL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  // USB 端口，Set PA11 and PA12 to AF10 (USB_FS)
  GPIO_InitStruct.Pin = LL_GPIO_PIN_11 | LL_GPIO_PIN_12;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

/*******************************************************************************
** 名    称 : USBD_Init
** 功    能 : USB 初始化
** 入口参数 : 无
** 出口参数 : 无
*******************************************************************************/
static void USBD_Configuration(void)
{

  /* Use PLL as USB PHY clock */
  LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLL_DIV_1_5);

  /* Enable USB power supply */
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USB);

  /* Peripheral interrupt init */
  NVIC_SetPriority(USB_HP_CAN1_TX_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 0));
  NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 0));

  NVIC_EnableIRQ(USB_HP_CAN1_TX_IRQn);
  NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
}

/*******************************************************************************
** 名    称 : UART_Configuration
** 功    能 : 串口 初始化
** 入口参数 : 无
** 出口参数 : 无
*******************************************************************************/
static void UART_Configuration(void)
{

  /* (1) Enable GPIO clock and configures the USART pins **********************/

  /* Enable the peripheral clock of GPIO Port */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);

  /* Enable USART2 peripheral clock and clock source ******************/
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1);

  /* Configure Tx Pin as : Alternate function, High Speed, Push pull, Pull up */
  LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_9, LL_GPIO_MODE_ALTERNATE);
  LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_9, LL_GPIO_SPEED_FREQ_HIGH);
  LL_GPIO_SetPinOutputType(GPIOA, LL_GPIO_PIN_9, LL_GPIO_OUTPUT_PUSHPULL);
  LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_9, LL_GPIO_PULL_UP);

  /* Configure Rx Pin as : Alternate function, High Speed, Push pull, Pull up */
  LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_10, LL_GPIO_MODE_FLOATING);
  LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_10, LL_GPIO_SPEED_FREQ_HIGH);
  LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_10, LL_GPIO_PULL_UP);

  /* (2) Configure USART2 functional parameters ********************************/

  /* Disable USART prior modifying configuration registers */
  /* Note: Commented as corresponding to Reset value */
  // LL_USART_Disable(USART2);
  NVIC_SetPriority(USART1_IRQn, 0);
  NVIC_EnableIRQ(USART1_IRQn);

  /* TX/RX direction */
  LL_USART_SetTransferDirection(USART1, LL_USART_DIRECTION_TX_RX);

  /* 8 data bit, 1 start bit, 1 stop bit, no parity */
  LL_USART_ConfigCharacter(USART1, LL_USART_DATAWIDTH_8B, LL_USART_PARITY_NONE, LL_USART_STOPBITS_1);

  /* No Hardware Flow control */
  /* Reset value is LL_USART_HWCONTROL_NONE */
  LL_USART_SetHWFlowCtrl(USART2, LL_USART_HWCONTROL_NONE);

  /* Set Baudrate to 115200 using APB frequency set to 36000000 Hz */
  /* Frequency available for USART peripheral can also be calculated through LL RCC macro */
  /* Ex :
  Periphclk = LL_RCC_GetUSARTClockFreq(Instance); or LL_RCC_GetUARTClockFreq(Instance); depending on USART/UART instance

  In this example, Peripheral Clock is expected to be equal to 36000000 Hz => equal to SystemCoreClock/2
  */
  LL_USART_SetBaudRate(USART1, SystemCoreClock, 115200);

  /* (3) Enable USART2 **********************************************************/
  LL_USART_ConfigAsyncMode(USART1); // 异步模式
  LL_USART_EnableIT_IDLE(USART1);   // 串口空闲中断
  LL_USART_Enable(USART1);          // 使能串口
}

/*******************************************************************************
** 名    称 : DMA_Configuration
** 功    能 : DMA初始化
** 入口参数 : 无
** 出口参数 : 无
*******************************************************************************/
static void DMA_Configuration(void)
{
  /* DMA1 used for USART1 Transmission and Reception */

  /* (1) Enable the clock of DMA1 */
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1); // 使能DMA1时钟

  /* (2) Configure NVIC for DMA transfer complete/error interrupts */
  NVIC_SetPriority(DMA1_Channel4_IRQn, 0);
  NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  NVIC_SetPriority(DMA1_Channel5_IRQn, 0);
  NVIC_EnableIRQ(DMA1_Channel5_IRQn);

  /* (3) Configure the DMA functional parameters for transmission */
  LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_4,             // 配置 DMA1 channel-6 进行串口数据发送
                        LL_DMA_DIRECTION_MEMORY_TO_PERIPH | // 传输模式 memory-to-peripheral
                            LL_DMA_PRIORITY_HIGH |          // channel 软件优先级
                            LL_DMA_MODE_NORMAL |            // 非循环模式，发完就停
                            LL_DMA_PERIPH_NOINCREMENT |     // 外设地址不增加
                            LL_DMA_MEMORY_INCREMENT |       // 内存地址增加
                            LL_DMA_PDATAALIGN_BYTE |        // 外设数据对齐方式为字节
                            LL_DMA_MDATAALIGN_BYTE);        // 内存数据对齐方式为字节
  LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_4,            // 配置发送数据的 DMA 通道的起始源地址和起始目的地址
                         (uint32_t)suart1.txbuf,
                         LL_USART_DMA_GetRegAddr(USART1),
                         LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_4));
  LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_4, USART_BUF_LEN); // 设置一次发送数据的长度 1028 bit

  LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_5,             // 配置 DMA1 channel-7 进行串口数据接收
                        LL_DMA_DIRECTION_PERIPH_TO_MEMORY | // 传输模式 peripheral-to-memory
                            LL_DMA_PRIORITY_HIGH |          // channel 软件优先级
                            LL_DMA_MODE_NORMAL |            // 非循环模式，收完就停
                            LL_DMA_PERIPH_NOINCREMENT |     // 外设地址不增加
                            LL_DMA_MEMORY_INCREMENT |       // 内存地址增加
                            LL_DMA_PDATAALIGN_BYTE |        // 外设数据对齐方式为字节
                            LL_DMA_MDATAALIGN_BYTE);        // 内存数据对齐方式为字节
  // 【XXX】：我感觉串口缓冲区是一个相对不变的地址，在 DMA 配置的时候进行绑定是合理的
  LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_5, // 配置接收数据的 DMA 通道的起始源地址和起始目的地址
                         LL_USART_DMA_GetRegAddr(USART1),
                         (uint32_t)suart1.rxbuf,
                         LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_5));
  LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, USART_BUF_LEN); // 设置一次接收数据的长度 1028 bit

  /* (4) Enable DMA interrupts  */
  LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_4); // 传输完成中断
  LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_4); // 传输错误中断
  LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_5);
  LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_5);

  /* (5) Enable the USART1 DMA TX and RX requests */
  // 【XXX】：在这里就启用通道好吗？要不要写一个释放逻辑呢？
  LL_USART_EnableDMAReq_RX(USART1); // 使能 USART1 DMA 接收
  LL_USART_EnableDMAReq_TX(USART1); // 使能 USART1 DMA 发送

  /* (6) Enable the DMA channels */
  LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5); // 启用 DMA1 的第 5 个通道用于接收数据
  LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_4); // 启用 DMA1 的第 4 个通道用于发送数据
}

/*******************************************************************************
** 名    称 ： CRC_Configuration
** 功    能 ： CRC 校验
** 入口参数 ： 无
** 出口参数 ： 无
*******************************************************************************/
static void CRC_Configuration(void)
{
  // CRC 时钟
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_CRC);
}

/*******************************************************************************
** 名    称 ： TIM_Configuration
** 功    能 ： 定时器配置
** 入口参数 ： 无
** 出口参数 ： 无
*******************************************************************************/
static void TIM_Configuration(void)
{
  /* TIM2 */
  // (1) 时钟初始化
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);
  // (2) 时基单元初始化
  LL_TIM_InitTypeDef TIM_InitStruct;
  TIM_InitStruct.Autoreload = 72 - 1;
  TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
  TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
  TIM_InitStruct.Prescaler = 1000 - 1;
  TIM_InitStruct.RepetitionCounter = 0;
  LL_TIM_Init(TIM2, &TIM_InitStruct);
  // (3) 定时器中断配置
  LL_TIM_EnableIT_UPDATE(TIM2);
  // (4) 配置 NVIC
  NVIC_SetPriority(TIM2_IRQn, 0);
  NVIC_EnableIRQ(TIM2_IRQn);
  // (5) 启动定时器计数
  LL_TIM_EnableCounter(TIM2);
}

/*******************************************************************************
** 名    称 ： SPI_Configuration
** 功    能 ： SPI 配置
** 入口参数 ： 无
** 出口参数 ： 无
*******************************************************************************/
static void SPI_Configuration(void)
{
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOB);

  LL_GPIO_InitTypeDef GPIO_InitStruct;
  // SPI1
  // PA4:输出 SPI1_NSS
  GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  // PA5:输出 SPI1_SCK
  GPIO_InitStruct.Pin = LL_GPIO_PIN_5;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  // PA6:输入，连接SDO
  GPIO_InitStruct.Pin = LL_GPIO_PIN_6;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_DOWN; // 无效配置，若 Mode 为 LL_GPIO_MODE_INPUT 则必须配置
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  // PA7:输出，连接 SDI
  GPIO_InitStruct.Pin = LL_GPIO_PIN_7;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  // SPI2
  // PB4:输入，连接 SDO
  GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_DOWN; // 无效配置，若 Mode 为 LL_GPIO_MODE_INPUT 则必须配置
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  // PB5:输出，连接 SDI
  GPIO_InitStruct.Pin = LL_GPIO_PIN_5;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  // PB6:输出 SPI2_SCK
  GPIO_InitStruct.Pin = LL_GPIO_PIN_6;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  // PB7:输出 SPI2_NSS
  GPIO_InitStruct.Pin = LL_GPIO_PIN_7;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  // PA15:输入，连接 INT
  GPIO_InitStruct.Pin = LL_GPIO_PIN_15;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING; // 让 ICM 自己拉
  GPIO_InitStruct.Pull = LL_GPIO_PULL_DOWN;     // 无效配置，若 Mode 为 LL_GPIO_MODE_INPUT 则必须配置
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

/*******************************************************************************
** 名    称 ： EXTI_Configuration
** 功    能 ： EXTI 配置
** 入口参数 ： 无
** 出口参数 ： 无
*******************************************************************************/
static void EXTI_Configuration(void)
{
  /* -1- GPIO Config */
  /* Enable GPIO Clock (to be able to program the configuration registers) */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
  /* Configure IO */
  LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_15, LL_GPIO_MODE_INPUT); // 设置 PA15 为输入模式

  /* -2- Connect External Line to the GPIO*/
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_AFIO);
  LL_GPIO_AF_SetEXTISource(LL_GPIO_AF_EXTI_PORTA, LL_GPIO_AF_EXTI_LINE15);

  /*-3- Enable a raising edge trigger EXTI line 15 Interrupt */
  LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_15);
  // LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_15); // falling edge trigger
  LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_15);     // raising edge trigger

  /*-4- Configure NVIC for EXTI15_10_IRQn */
  NVIC_EnableIRQ(EXTI15_10_IRQn);
  NVIC_SetPriority(EXTI15_10_IRQn, 0);
}

/*******************************************************************************
** 名    称 ： BSP_Init
** 功    能 ： 板级支持包初始化
** 入口参数 ： 无
** 出口参数 ： 无
*******************************************************************************/
void BSP_Init(void)
{
  // IO功能初始化
  GPIO_Configuration();

  // 串口配置
  UART_Configuration();

  // DMA 配置
  DMA_Configuration();

  // EXTI 配置
  EXTI_Configuration();

  // SPI 配置
  SPI_Configuration();

  // 定时器配置
  TIM_Configuration();

  // USB 初始化
  USBD_Configuration();

  // CRC 校验
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_CRC);

  // 初始化小灯状态(无 BUG 熄灭)
  LL_GPIO_SetOutputPin(GPIOB, LL_GPIO_PIN_0);
}

#ifdef USE_FULL_ASSERT

/**
 * @brief  Reports the name of the source file and the source line number
 *         where the assert_param error has occurred.
 * @param  file: pointer to the source file name
 * @param  line: assert_param error line source number
 * @retval None
 */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d", file, line) */

  /* Infinite loop */
  while (1)
  {
  }
}
#endif

// #ifdef __GNUC__
// void _init(void) {}
// #endif

/**
 * @}
 */

/**
 * @}
 */
